// This is an open source non-commercial project. Dear PVS-Studio, please check it.
// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com

// ReSharper disable CheckNamespace
// ReSharper disable ClassNeverInstantiated.Global
// ReSharper disable CommentTypo
// ReSharper disable IdentifierTypo
// ReSharper disable InconsistentNaming
// ReSharper disable StringLiteralTypo
// ReSharper disable UnusedParameter.Local

/*
 * Ars Magna project, http://arsmagna.ru
 */

#region Using directives

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;

using AM.Reporting.Utils;
using AM.Reporting.Preview;

using System.Drawing;

#endregion

#nullable enable

namespace AM.Reporting.Export
{
    /// <summary>
    /// The base class for all export filters.
    /// </summary>
    public partial class ExportBase : Base
    {
        private List<int> pages;
        private int exportTickCount;
        protected bool webPreview;

        #region Properties

        /// <summary>
        /// Gets list of generated streams.
        /// </summary>
        public List<Stream> GeneratedStreams { get; protected set; }

        /// <summary>
        /// Zoom factor for output file
        /// </summary>
        public float Zoom { get; set; }

        /// <summary>
        /// File filter that can be used in the "Save file" dialog.
        /// </summary>
        public string FileFilter => GetFileFilter();

        /// <summary>
        /// Range of pages to export.
        /// </summary>
        public PageRange PageRange { get; set; }

        /// <summary>
        /// Page numbers to export.
        /// </summary>
        /// <remarks>
        /// Use page numbers separated by comma and/or page ranges, for example: "1,3-5,12". Empty string means
        /// that all pages need to be exported.
        /// </remarks>
        public string PageNumbers { get; set; }

        /// <summary>
        /// Current page number.
        /// </summary>
        /// <remarks>
        /// Page number need to be exported if user selects "Current page" radiobutton in the export options dialog.
        /// This property is typically set to current page number in the preview window.
        /// </remarks>
        public int CurPage { get; set; }

        /// <summary>
        /// Open the document after export.
        /// </summary>
        public bool OpenAfterExport { get; set; }

        /// <summary>
        /// Allows or disables the OpenAfterExport feature.
        /// </summary>
        public bool AllowOpenAfter { get; set; }

        /// <summary>
        /// Gets or sets a value that determines whether to show progress window during export or not.
        /// </summary>
        public bool ShowProgress { get; set; }

        /// <summary>
        /// Gets a list of files generated by this export.
        /// </summary>
        public List<string> GeneratedFiles { get; }

        /// <summary>
        /// Gets a value indicating that the export may produce multiple output files.
        /// </summary>
        public bool HasMultipleFiles { get; set; }

        /// <summary>
        /// Gets or sets a value indicating that the report bands should be shifted, if page
        /// has any non-exportable bands
        /// </summary>
        public bool ShiftNonExportable { get; set; }

        /// <summary>
        /// Gets or sets the initial directory that is displayed by a save file dialog.
        /// </summary>
        public string SaveInitialDirectory { get; set; }

        /// <summary>
        /// Gets or sets a value indicating that pages will exporting from all open tabs.
        /// </summary>
        public bool ExportAllTabs { get; set; }

        /// <summary>
        /// Stream to export to.
        /// </summary>
        protected Stream Stream { get; private set; }

        /// <summary>
        /// File name to export to.
        /// </summary>
        protected string FileName { get; private set; }

        /// <summary>
        /// Array of page numbers to export.
        /// </summary>
        protected int[] Pages => pages.ToArray();

        internal bool AllowSaveSettings { get; set; }

        #endregion

        #region Private Methods

        private bool Parse (string pageNumbers, int total)
        {
            pages.Clear();
            var s = pageNumbers.Replace (" ", "");
            if (s == "")
            {
                return false;
            }

            if (s[s.Length - 1] == '-')
            {
                s += total.ToString();
            }

            s += ',';

            var i = 0;
            var j = 0;
            var n1 = 0;
            var n2 = 0;
            var isRange = false;

            while (i < s.Length)
            {
                if (s[i] == ',')
                {
                    n2 = int.Parse (s.Substring (j, i - j));
                    j = i + 1;
                    if (isRange)
                    {
                        while (n1 <= n2)
                        {
                            pages.Add (n1 - 1);
                            n1++;
                        }
                    }
                    else
                    {
                        pages.Add (n2 - 1);
                    }

                    isRange = false;
                }
                else if (s[i] == '-')
                {
                    isRange = true;
                    n1 = int.Parse (s.Substring (j, i - j));
                    j = i + 1;
                }

                i++;
            }

            return true;
        }

        private void PreparePageNumbers()
        {
            pages.Clear();
            var total = Report.PreparedPages.Count;
            if (PageRange == PageRange.Current)
            {
                pages.Add (CurPage - 1);
            }
            else if (!Parse (PageNumbers, total))
            {
                for (var i = 0; i < total; i++)
                {
                    pages.Add (i);
                }
            }

            // remove invalid page numbers
            for (var i = 0; i < pages.Count; i++)
            {
                if (pages[i] < 0 || pages[i] >= total)
                {
                    pages.RemoveAt (i);
                    i--;
                }
            }
        }

        private void OpenFile()
        {
            try
            {
                var proc = new Process();
                proc.EnableRaisingEvents = false;

#if NETCOREAPP
                proc.StartInfo.FileName = "cmd";
                proc.StartInfo.Arguments = $"/c \"{FileName}\"";
                proc.StartInfo.CreateNoWindow = true;
#else
                proc.StartInfo = new ProcessStartInfo(fileName) { UseShellExecute = true };
#endif
                proc.Start();
            }
            catch
            {
            }
        }

        private void SaveSettings()
        {
            var root = Config.Root.FindItem ("Preview").FindItem ("Exports").FindItem (ClassName);
            using (var writer = new ReportWriter (root))
            {
                root.Clear();
                writer.Write (this);
            }
        }

        private void RestoreSettings()
        {
            var root = Config.Root.FindItem ("Preview").FindItem ("Exports").FindItem (ClassName);
            using (var reader = new ReportReader (null, root))
            {
                reader.Read (this);
            }
        }

        #endregion

        #region Protected Methods

        /// <summary>
        /// Returns a file filter for a save dialog.
        /// </summary>
        /// <returns>String that contains a file filter, for example: "Bitmap image (*.bmp)|*.bmp"</returns>
        protected virtual string GetFileFilter()
        {
            return "";
        }

        /// <summary>
        /// This method is called when the export starts.
        /// </summary>
        protected virtual void Start()
        {
            Report.OnExportParameters (new ExportParametersEventArgs (this));
        }

        /// <summary>
        /// This method is called at the start of exports of each page.
        /// </summary>
        /// <param name="page">Page for export may be empty in this method.</param>
        protected virtual void ExportPageBegin (ReportPage page)
        {
            page = GetOverlayPage (page);
        }

        /// <summary>
        /// This method is called at the end of exports of each page.
        /// </summary>
        /// <param name="page">Page for export may be empty in this method.</param>
        protected virtual void ExportPageEnd (ReportPage page)
        {
        }

        /// <summary>
        /// This method is called for each band on exported page.
        /// </summary>
        /// <param name="band">Band, dispose after method compite.</param>
        protected virtual void ExportBand (BandBase band)
        {
            band.UpdateWidth();
        }

        /// <summary>
        /// This method is called when the export is finished.
        /// </summary>
        protected virtual void Finish()
        {
        }

        /// <summary>
        /// Gets a report page with specified index.
        /// </summary>
        /// <param name="index">Zero-based index of page.</param>
        /// <returns>The prepared report page.</returns>
        protected ReportPage GetPage (int index)
        {
            var page = Report.PreparedPages.GetPage (index);
            return GetOverlayPage (page);
        }

        #endregion

        #region Public Methods

        /// <inheritdoc/>
        public override void Assign (Base source)
        {
            BaseAssign (source);
        }

        /// <inheritdoc/>
        public override void Serialize (ReportWriter writer)
        {
            writer.WriteValue ("PageRange", PageRange);
            writer.WriteStr ("PageNumbers", PageNumbers);
            writer.WriteBool ("OpenAfterExport", OpenAfterExport);
            writer.WriteBool ("ExportAllTabs", ExportAllTabs);
        }

        /// <summary>
        /// Exports the report to a stream.
        /// </summary>
        /// <param name="report">Report to export.</param>
        /// <param name="stream">Stream to export to.</param>
        /// <remarks>
        /// This method does not show an export options dialog. If you want to show it, call <see cref="ShowDialog"/>
        /// method prior to calling this method, or use the "Export(Report report)" method instead.
        /// </remarks>
        public void Export (Report report, Stream stream)
        {
            if (report == null || report.PreparedPages == null)
            {
                return;
            }

            SetReport (report);
            this.Stream = stream;
            PreparePageNumbers();
            GeneratedFiles.Clear();
            exportTickCount = Environment.TickCount;

            if (pages.Count > 0)
            {
                if (!string.IsNullOrEmpty (FileName))
                {
                    GeneratedFiles.Add (FileName);
                }

                Start();
                report.SetOperation (ReportOperation.Exporting);

                if (ShowProgress)
                {
                    Config.ReportSettings.OnStartProgress (Report);
                }
                else
                {
                    Report.SetAborted (false);
                }

                try
                {
                    for (var i = 0; i < GetPagesCount (pages); i++)
                    {
                        if (ShowProgress)
                        {
                            Config.ReportSettings.OnProgress (Report,
                                string.Format (Res.Get ("Messages,ExportingPage"), i + 1, pages.Count), i + 1,
                                pages.Count);
                        }

                        if (!Report.Aborted && i < pages.Count)
                        {
                            ExportPageNew (pages[i]);
                        }
                        else
                        {
                            break;
                        }
                    }
                }
                finally
                {
                    Finish();

                    if (ShowProgress)
                    {
                        Config.ReportSettings.OnProgress (Report, string.Empty);
                    }

                    if (ShowProgress)
                    {
                        Config.ReportSettings.OnFinishProgress (Report);
                    }

                    report.SetOperation (ReportOperation.None);

                    exportTickCount = Environment.TickCount - exportTickCount;

                    ShowPerformance (exportTickCount);

                    if (OpenAfterExport && AllowOpenAfter && stream != null)
                    {
                        stream.Close();
                        OpenFile();
                    }
                }
            }
        }

        public void InstantExportStart (Report report, Stream stream)
        {
            SetReport (report);
            this.Stream = stream;
            GeneratedFiles.Clear();
            if (!string.IsNullOrEmpty (FileName))
            {
                GeneratedFiles.Add (FileName);
            }

            Start();
        }

        public void InstantExportBeginPage (ReportPage page)
        {
            ExportPageBegin (page);
        }

        public void InstantExportExportBand (BandBase band)
        {
            ExportBand (band);
        }

        public void InstantExportEndPage (ReportPage page)
        {
            ExportPageEnd (page);
        }

        public void InstantExportFinish()
        {
            Finish();
        }

        internal void ExportPageNew (int pageNo)
        {
            var ppage = Report.PreparedPages.GetPreparedPage (pageNo);
            {
                ReportPage page = null;
                try
                {
                    page = ppage.StartGetPage (pageNo);
                    page.Width = ppage.PageSize.Width;
                    page.Height = ppage.PageSize.Height;
                    ExportPageBegin (page);
                    float topShift = 0;
                    foreach (var obj in ppage.GetPageItems (page, false))
                    {
                        var band = obj as BandBase;
                        if (ShiftNonExportable && topShift != 0 && obj is BandBase and not PageFooterBand && !band.PrintOnBottom)
                        {
                            band.Top -= topShift;
                        }

                        if (band.Exportable
                            || webPreview)
                        {
                            ExportBand (band);
                        }
                        else if (obj != null)
                        {
                            if (ShiftNonExportable)
                            {
                                topShift += band.Height;
                            }

                            obj.Dispose();
                        }
                    }

                    ExportPageEnd (page);
                }
                finally
                {
                    ppage.EndGetPage (page);
                }

                if (page != null)
                {
                    page.Dispose();
                }
            }
        }

        /// <summary>
        /// Exports the report to a file.
        /// </summary>
        /// <param name="report">Report to export.</param>
        /// <param name="fileName">File name to export to.</param>
        /// <remarks>
        /// This method does not show an export options dialog. If you want to show it, call <see cref="ShowDialog"/>
        /// method prior to calling this method, or use the "Export(Report report)" method instead.
        /// </remarks>
        public void Export (Report report, string fileName)
        {
            this.FileName = fileName;
            using (var stream = new FileStream (fileName, FileMode.Create))
            {
                Export (report, stream);
                stream.Close();
            }
        }

        internal string GetFileName (Report report)
        {
            return Path.GetFileNameWithoutExtension (Path.GetFileName (report.FileName));
        }

        internal string GetFileExtension()
        {
            var extension = FileFilter;
            return extension.Substring (extension.LastIndexOf ('.'));
        }

        internal void ExportAndZip (Report report, Stream stream)
        {
            var tempFolder = Config.GetTempFolder() + Path.GetRandomFileName();
            Directory.CreateDirectory (tempFolder);
            try
            {
                var filePath = Path.Combine (tempFolder, GetFileName (report) + GetFileExtension());
                Export (report, filePath);
                var zip = new ZipArchive();
                zip.AddDir (tempFolder);
                zip.SaveToStream (stream);
            }
            finally
            {
                Directory.Delete (tempFolder, true);
            }
        }

        #endregion

        /// <summary>
        /// Initializes a new instance of the <see cref="ExportBase"/> class.
        /// </summary>
        public ExportBase()
        {
            PageNumbers = "";
            pages = new List<int>();
            CurPage = 1;
            FileName = "";
            AllowOpenAfter = true;
            Zoom = 1;
            GeneratedFiles = new List<string>();
            ExportAllTabs = false;
            ShiftNonExportable = false;
        }
    }
}
